package com.elitesland.core.util.excel;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.elitesland.core.util.SpringContextHolder;
import com.elitesland.core.util.excel.support.ExportColumnParam;
import com.elitesland.core.util.excel.support.LocalDateTimeConverter;
import com.elitesland.model.SysConfigProperties;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * excel导出工具类.
 * <p>
 * 基于EasyExcel实现的工具类，
 * {@see https://www.yuque.com/easyexcel/doc/easyexcel}
 *
 * @author Kaiser（wang shao）
 * @date 2020-12-31
 */
public class ExcelExportUtil {

    private final Logger logger = LoggerFactory.getLogger(ExcelExportUtil.class);

    private final ExcelWriterBuilder builder;
    private List<String> fields;

    private ExcelExportUtil(OutputStream outputStream) {
        builder = EasyExcelFactory.write(outputStream)
                .excelType(ExcelTypeEnum.XLSX)
                .autoCloseStream(true)
                .registerConverter(new LocalDateTimeConverter())
        ;
    }

    /**
     * 导出工具实例化
     *
     * @param response 导出下载时
     * @param fileName 导出后的文件名
     * @return 工具实例
     * @throws IOException
     */
    public static ExcelExportUtil instance(HttpServletResponse response, String fileName) throws IOException {
        // 这里注意 有同学反应使用swagger 会导致各种问题，请直接用浏览器或者用postman
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

        return new ExcelExportUtil(response.getOutputStream());
    }

    /**
     * 数据的标题
     *
     * @param columnParamList 导出的列
     * @return
     */
    public ExcelExportUtil fields(List<ExportColumnParam> columnParamList) {
        if (columnParamList != null && !columnParamList.isEmpty()) {
            List<List<String>> titles = new ArrayList<>(64);
            List<String> includeFields = new ArrayList<>(64);
            for (ExportColumnParam p : columnParamList) {
                if (StrUtil.isBlank(p.getTitle()) || StrUtil.isBlank(p.getField())) {
                    continue;
                }
                titles.add(List.of(p.getTitle()));
                includeFields.add(p.getField());
            }
            this.builder.head(titles);
            this.fields = includeFields;
        }
        return this;
    }

    /**
     * 开始写数据
     *
     * @param sheetName sheet名称
     * @param data      数据
     */
    public void write(String sheetName, List<?> data) {
        long start = System.currentTimeMillis();

        List<List<String>> dataConvert = convertData(data, fields);

        this.builder.sheet(sheetName).doWrite(dataConvert);

        logger.info("excel导出完毕，用时：{}ms", System.currentTimeMillis() - start);
    }

    private List<List<String>> convertData(List<?> dataList, List<String> fields) {
        if (CollectionUtils.isEmpty(dataList) || CollectionUtils.isEmpty(fields)) {
            return Collections.emptyList();
        }

        // 获取导出数量限制
        Integer limit = getLimitNumOfExport();
        logger.info("导出数量限制：{}", limit);
        if (limit != null) {
            if (limit < 1) {
                return Collections.emptyList();
            }
            dataList = CollectionUtil.sub(dataList, 0, limit);
        }

        return getObjectMapper().convertValue(dataList, new TypeReference<List<Map<String, Object>>>() {
        }).stream().map(data ->
                fields.stream().map(field -> obtainValue(data, field)
                ).collect(Collectors.toList())
        ).collect(Collectors.toList());
    }

    /**
     * 获取导出数量的限制
     *
     * @return
     */
    private Integer getLimitNumOfExport() {
        SysConfigProperties configProperties = SpringContextHolder.getBean(SysConfigProperties.class);
        return configProperties.getExport().getMaxRows();
    }

    private String obtainValue(Map<String, Object> data, String field) {
        Object value = data.get(field);
        if (value == null) {
            return "";
        }
        if (value instanceof LocalDateTime) {
            return dtf_ymd.format((LocalDateTime) value);
        } else if (value instanceof Double) {
            return NumberUtil.decimalFormatMoney((Double) value);
        } else if (value instanceof Float) {
            return NumberUtil.decimalFormatMoney((Float) value);
        }
        return value.toString();
    }

    private ObjectMapper getObjectMapper() {
        return new Jackson2ObjectMapperBuilder()
                .serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(dtf_ymdHms))
                .deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(dtf_ymdHms))
                .build();
    }

    private final DateTimeFormatter dtf_ymd = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private final DateTimeFormatter dtf_ymdHms = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
}
